/* This file is part of FireEscape.
 *
 * FireEscape is the intellectual work of Phillip Cheng & Allen Park.
 * All future releases or modifications of this program must include this
 * attribuation as according to the Creative Commons BY-SA license.
 *
 * FireEscape is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * FireEscape is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with FireEscape.  If not, see <http://www.gnu.org/licenses/>.
 *
 * All modifications to this program should include this original statement.
 *
 * If you modify, distribute, or use the program , please contact
 * fireescape@kloudvine.com
 * http://code.google.com/p/fireescape/
 *
 * If you are interested in using FireEscape for your structure, please contact us
 * for support.
 *
 * If you do use FireEscape, please consider donating to the project.
 */
package fireescape;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Provides information for each Handler thread. Stores the information to
 * pass packets to the client, such as each genome.
 * @author chengP
 */
public class ServerAI {

    public static int simulation = 1;
    public static int generations = 1;
    public static int geneticIterations = 1;
    public static int scrollVar = 0;
    //[SPACE]
    private static final int sizeOfGen = 1600;
    //[SPACE]
    private static List<Route> routes = new ArrayList<Route>();
    private static int numberRoutes;
    private static int genomeID = -1;
    private static List<Genome> genome = new ArrayList<Genome>();
    public static int numReturned = 0;

    /**
     * Returns the genome ID
     * @return The genome ID
     */
    public static synchronized int getGenomeID() {
        return genomeID;
    }

    /**
     * Sets the routes
     * @param routes The new routes
     */
    public static void setRoutes(ArrayList<Route> routes) {
        ServerAI.routes = routes;
    }

    /**
     * Sets the number of routes
     * @param numRoutes The number of routes
     */
    public static void setNumRoutes(int numRoutes) {
        numberRoutes = numRoutes;
    }

    /**
     * Returns the number of routes
     * @return The number of routes
     */
    public static int getNumRoutes() {
        return numberRoutes;
    }

    /**
     * Adds a new route
     * @param route The new route
     */
    public static void addRoute(Route route) {
        routes.add(route);
    }

    /**
     * Returns a route
     * @param n The route ID
     * @return The route
     */
    public static Route getRoute(int n) {
        return routes.get(n);
    }

    /**
     * Initializes the server AI
     */
    public static void init() {
        genome.add(Map.generateCloseRoutes());
                    routes = new ArrayList<Route>();//reset
            Logger.getLogger("FireEscapeLog").log(Level.INFO, "Created closest route");
        for (int k = 1; k < sizeOfGen; k++) {
            //System.out.println("got here");
            Map.generateRoutes();
            //System.out.println("got here2");
            genome.add(new Genome(routes.hashCode(), routes, 0, false, k));
            routes = new ArrayList<Route>();//reset
            Logger.getLogger("FireEscapeLog").log(Level.INFO, "Created a new route");
        }
    }

    /**
     * Returns the packet
     * Creates a new generation if needed based on the global variables
     * GeneticID and the size of the current genome.
     * @return The packet
     */
    public static synchronized Packet getPacket() {
        geneticIterations++;
        genomeID++;
        System.out.println(generations + ": "+String.valueOf((double)genomeID/(double)genome.size()*100) +"% Complete");
        if (genomeID < genome.size()) {
            routes = new ArrayList<Route>();
            routes = genome.get(genomeID).getList();

            reset();
            Logger.getLogger("FireEscapeLog").log(Level.INFO, "Returning a genome from list");
            //System.out.println("Returning genome of size:" + genome.get(genomeID).getList().size());
            return new Packet("procees", genome.get(genomeID));
        } else {
            Logger.getLogger("FireEscapeLog").log(Level.INFO, "Creating a new generation");
            int tryCount = 0;
            if (numReturned < genome.size()) {
                Logger.getLogger("FireEscapeLog").log(Level.WARNING, "Late client interception");
                while (numReturned < genome.size()) {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException ex) {
                        Logger.getLogger("FireEscapeLog").log(Level.SEVERE, null, ex);
                    }
                    tryCount++;
                    if (tryCount > 10) {
                        Logger.getLogger("FireEscapeLog").log(Level.WARNING, "Lost client");
                        for (Genome g : genome) {
                            if (g.iterated() == false) {
                                Logger.getLogger("FireEscapeLog").log(Level.WARNING, "Lost client was processing: " + g.getHash());
                                routes = new ArrayList<Route>();
                                routes = g.getList();
                                reset();
                                genomeID--;
                                geneticIterations--;
                                return new Packet("process", g);

                            }
                        }

                    }
                    //System.out.println("waiting");
                }
            }
            tryCount = 0;
            generations++;
            Collections.sort(genome, new GenomeComparator());
            int z = genome.size();
            for (int k = genome.size() - 1; k > (int) (z * .2); k--) {
                genome.remove(k);
            }
            int y = genome.size();
            for (int k = 0; k <= y; k++) {
                Genome motherGenome = genome.get(k);
                int fatherIndex = (int) (Math.random() * y);
                Genome fatherGenome = genome.get(fatherIndex);
                ArrayList mother = (ArrayList) motherGenome.getList();
                ArrayList father = (ArrayList) fatherGenome.getList();
                ArrayList child = new ArrayList<Route>();
                double mTime = genome.get(k).getTime();
                double fTime = genome.get(fatherIndex).getTime();
                for (int j = 0; j < mother.size(); j++) {
                    Route r = (Route) mother.get(j);
                    double MotherIterTime = r.getSlowestTime();
                    Route rb = (Route) father.get(j);
                    double FatherIterTime = rb.getSlowestTime();
                    if ((MotherIterTime < FatherIterTime) || (r.getRouteSize() < rb.getRouteSize())) {
                        child.add(r);
                    } else {
                        child.add(rb);
                    }
                }

                Genome kid = new Genome(child.hashCode(), child, 0, false, genome.size());
                kid.setMotherHash(motherGenome.getHash());
                kid.setFatherHash(fatherGenome.getHash());
                kid.setMotherTime(mTime);
                kid.setFatherTime(fTime);
                Logger.getLogger("FireEscapeLog").log(Data.DATA, "gen: " + generations + " : " + kid.getHash() + "=" + motherGenome.getHash() + "*" + fatherGenome.getHash());
                genome.add(kid);

            }
            for (int k = 0; k <= y; k++) {
                Genome motherGenome = genome.get(k);
                int fatherIndex = (int) (Math.random() * y);
                Genome fatherGenome = genome.get(fatherIndex);
                ArrayList mother = (ArrayList) motherGenome.getList();
                ArrayList father = (ArrayList) fatherGenome.getList();
                ArrayList child = new ArrayList<Route>();
                double mTime = genome.get(k).getTime();
                double fTime = genome.get(fatherIndex).getTime();
                for (int j = 0; j < (int) (mother.size()); j++) {
                    Route r = (Route) mother.get(j);
                    Route rb = (Route) father.get(j);
                    if (Math.random() < .5) {
                        child.add(r);
                    } else {
                        child.add(rb);
                    }
                }

                Genome kid = new Genome(child.hashCode(), child, 0, false, genome.size());
                kid.setMotherHash(motherGenome.getHash());
                kid.setFatherHash(fatherGenome.getHash());
                kid.setMotherTime(mTime);
                kid.setFatherTime(fTime);
                Logger.getLogger("FireEscapeLog").log(Data.DATA, "gen: " + generations + " : " + kid.getHash() + "=" + motherGenome.getHash() + "*" + fatherGenome.getHash());
                genome.add(kid);
            }

            while (genome.size() < sizeOfGen) {
                Map.generateRoutes();
                genome.add(new Genome(routes.hashCode(), routes, 0, false, genome.size()));
                routes = new ArrayList<Route>();
            }

            for (int k = 0; k < genome.size(); k++) {
                genome.set(k, new Genome(genome.get(k).getHash(), genome.get(k).getList(), 0, false, k));
            }

            System.gc();
            routes = genome.get(0).getList();
            numReturned = 0;
            reset();
            genomeID = 0;
            Logger.getLogger("FireEscapeLog").log(Level.INFO, "Returning a freshly created genome");
            return new Packet("process", genome.get(genomeID));

        }
    }

    /**
     * Resets the map and bots
     */
    public static void reset() {
        Map.reset();
        BotAI.reset();
    }

    /**
     * Replaces the genome. Performs a hash check before replace.
     * @param g The new genome
     */
    public static synchronized void replaceGenome(Genome g) {
        if (g.getHash() == genome.get(g.getGenomeID()).getHash()) {
            Logger.getLogger("FireEscapeLog").log(Level.INFO, "Replaced genome:" + g.getGenomeID());
            Logger.getLogger("FireEscapeLog").log(Data.DATA, "gen: " + generations + " : " + g.getHash() + "," + g.hashCode() + "@t: " + g.getTime());
            genome.set(g.getGenomeID(), g);
            numReturned++;
        }
    }
}
